home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / Main.bin / CurrencyEngine.java < prev    next >
Text File  |  1998-11-06  |  12KB  |  281 lines

  1. /*
  2.   Engine class for support of currency field input.
  3.  
  4.   Created 5/4/98 by Paul Lancaster.
  5. */
  6.  
  7. package com.symantec.itools.swing;
  8.  
  9. import java.math.BigDecimal;   // used for rounding
  10.  
  11. public class CurrencyEngine {
  12.   public CurrencyEngine() {}  // default constructor
  13.  
  14.   public void    setCommas            (boolean b) { _commas              = b    ; }
  15.   public void    setCurrencyLeading   (boolean b) { _currencySymbolLeads = b    ; }
  16.   public void    setATMmode           (boolean b) { _ATMmode             = b    ; }
  17.   public void    setCurrencySymbol    (String  s) { _currencySymbol      = s    ; }
  18.   public void    setDecimalPoint      (char    c) { _decimalPoint        = c    ; }
  19.   public void    setSeparator         (char    c) { _separator           = c    ; }
  20.   public void    setDigitsAfterDecimal(int     c) { _digitsAfterDecimal  = c    ; }
  21.   public boolean getCommas            (         ) { return _commas              ; }
  22.   public boolean getCurrencyLeading   (         ) { return _currencySymbolLeads ; }
  23.   public boolean getATMmode           (         ) { return _ATMmode             ; }
  24.   public String  getCurrencySymbol    (         ) { return _currencySymbol      ; }
  25.   public char    getDecimalPoint      (         ) { return _decimalPoint        ; }
  26.   public char    getSeparator         (         ) { return _separator           ; }
  27.   public int     getDigitsAfterDecimal(         ) { return _digitsAfterDecimal  ; }
  28.  
  29.   /*  Called to initialize the display of the masked data.
  30.       The first parameter is the current data in the field.
  31.       The second parameter holds the string that should be displayed.
  32.       The return value is the initial caret position.
  33.   */
  34.   public int initDisplay(String data, StringBuffer newData) {
  35.     if (data.length() == 0) {   // no input data
  36.       normalize(newData);
  37.     } else {  // data coming in
  38.       newData.append(data);
  39.       scale(newData);
  40.     }
  41.     return _ATMmode ? newData.length() : 0;
  42.   }
  43.  
  44.   /* This is the main workhorse method.
  45.      It's called for every key stroke corresponding to displayable
  46.      characters once editing begins.
  47.      The 1st parameter is the user keystroke event object.
  48.      The 2nd parameter is the current cursor position (zero based).
  49.      The 3rd parameter is the current text from the component.
  50.      The 4th parameter is output and is what should be displayed in the component.
  51.      The 5th and 6th parameters are the selection start and end, respectively.
  52.      The return value is the new cursor position within the "newData"
  53.      parameter (zero based), unless it is negative, in which case:
  54.      -1 means the input is inconsistent.
  55.   */
  56.   public int processKey(java.awt.event.KeyEvent e, int pos, String data,
  57.                         StringBuffer newData, int selStart, int selEnd) {
  58.     char key = e.getKeyChar();
  59.     newData.append(data);    // init output to input
  60.     int decpos = data.indexOf(_decimalPoint);
  61.     int datalen = data.length();
  62.     int keyCode = e.getKeyCode();
  63.     if (_ATMmode) {
  64.       switch (keyCode) {
  65.       case e.VK_LEFT:
  66.         return pos - (pos == 0 ? pos : (pos == decpos + 1 ? 2 : 1));
  67.       case e.VK_RIGHT:
  68.         return pos + (pos == datalen ? 0 : (pos == decpos - 1 ? 2 : 1));
  69.       case e.VK_BACK_SPACE:
  70.       case e.VK_DELETE:
  71.         if (pos == decpos)  // don't allow decimal point deletion
  72.           return -1;
  73.         if (selStart < selEnd) {
  74.           clearSelectedText(selStart, selEnd, newData, true);
  75.           return selStart;
  76.         }
  77.         deleteChar(newData, pos == datalen ? pos - 1 : pos);
  78.         normalize(newData);
  79.         int delta = newData.length() - datalen;
  80.         return pos == decpos - 1 ? (decpos + (0 == delta ? 1 : 0)) : pos + delta;
  81.       }  // end switch on navigation key
  82.     } else {  // not ATM mode
  83.       // Have to handle backspace & delete in non-ATM mode to ensure
  84.       // right justification occurs if component is awt.TextField
  85.       switch (keyCode) {
  86.       case e.VK_BACK_SPACE:
  87.         if (selEnd == selStart && pos > 0) {
  88.           clearSelectedText(pos - 1, pos, newData, false);
  89.           return pos - 1;
  90.         } else {
  91.           clearSelectedText(selStart, selEnd, newData, false);
  92.           return pos;
  93.         }
  94.       case e.VK_DELETE:
  95.         if (selEnd == selStart && pos < datalen)
  96.           clearSelectedText(pos, pos + 1, newData, false);
  97.         else
  98.           clearSelectedText(selStart, selEnd, newData, false);
  99.         return pos;
  100.       }  // end switch on navigation key
  101.     }
  102.     if (!Character.isDigit(key)) {  // input not a digit?
  103.       if (key != _decimalPoint || _ATMmode)
  104.         return -1;    // must be decimal point if not digit
  105.       if (decpos != -1)
  106.         return pos <= decpos ? decpos + 1 : -1;
  107.     }
  108.     clearSelectedText(selStart, selEnd, newData, true);
  109.     if (_ATMmode) {
  110.       //if (selStart < selEnd && decpos < selStart) {
  111.       if (selStart < selEnd ) {
  112.         //this is a quick fix to avoid StringIndexOutOfBoundsExceptions
  113.         //@todo:handle cases (properly) when a digit to the left of decimal
  114.         //      separator shifts to the right when selection is deleted
  115.         newData.insert(selStart, key);
  116.       }else{
  117.         newData.insert(pos, key);
  118.       }
  119.       normalize(newData);
  120.       return selStart < selEnd ? selStart : pos + newData.length() - datalen;
  121.     } else{
  122.       //newData.insert(pos, key);
  123.       //this is a quick fix to avoid StringIndexOutOfBoundsExceptions
  124.       if (selStart < selEnd ) {
  125.         pos = selStart;
  126.         newData.insert(selStart, key);
  127.       }else{
  128.         newData.insert(pos, key);
  129.       }
  130.     }
  131.     return pos + 1;
  132.   }
  133.  
  134.   // Return true iff the engine handles the given key stroke.
  135.   public boolean isHandledKey(java.awt.event.KeyEvent e) {
  136.     if (!Character.isISOControl(e.getKeyChar()))
  137.       return true;  // we handle all non-control characters
  138.     switch (e.getKeyCode()) {  // here are the controls we handle
  139.     case e.VK_BACK_SPACE:
  140.     case e.VK_DELETE:
  141.       return true;
  142.     case e.VK_RIGHT:
  143.     case e.VK_LEFT:
  144.       return _ATMmode && !e.isShiftDown();
  145.     }
  146.     return false;  // don't handle control chars by default
  147.   }
  148.  
  149.   public void postFormat(String data, StringBuffer newData) {
  150.     newData.append(data);
  151.     scale(newData);
  152.     data = newData.toString();
  153.     newData.setLength(0);
  154.     if (_currencySymbolLeads)
  155.       newData.append(_currencySymbol);
  156.     int j = newData.length();
  157.     newData.append(data);
  158.     if (!_currencySymbolLeads)
  159.       newData.append(_currencySymbol);
  160.     int decpos = data.indexOf(_decimalPoint);
  161.     for (int i = 0; i < decpos; i++, j++)
  162.       if (_commas && i > 0 && i % 3 == decpos % 3)
  163.         newData.insert(j++, _separator);
  164.   }
  165.  
  166.   public void cut(StringBuffer newData, int selStart, int selEnd) {
  167.     clearSelectedText(selStart, selEnd, newData, true);
  168.   }
  169.  
  170.   public void paste(StringBuffer data, String pasteData, int pos, int selStart, int selEnd) {
  171.     clearSelectedText(selStart, selEnd, data, false);
  172.     String s = data.toString();
  173.     int decpos = s.indexOf(_decimalPoint);
  174.     data.setLength(0);
  175.  
  176.     // Sanitize the paste data to remove non-digits.
  177.     StringBuffer pd = new StringBuffer();
  178.     int pastelen = pasteData.length();
  179.     for (int i = 0; i < pastelen; i++) {
  180.       char c = pasteData.charAt(i);
  181.       if (Character.isDigit(c) || (decpos == -1 && _decimalPoint == c))
  182.         pd.append(c);
  183.     }
  184.  
  185.     data.append(s.substring(0, pos) + pd.toString() + s.substring(pos, s.length()));
  186.     if (_ATMmode)
  187.       normalize(data);
  188.   }
  189.  
  190.   void clearSelectedText(int selStart, int selEnd, StringBuffer data, boolean norm) {
  191.     int selLen = selEnd - selStart;
  192.     if (selLen > 0) {            // only if selected text exists
  193.       String s = data.toString();
  194.       int oldlen = data.length();
  195.       data.insert(0, s.substring(0, selStart) + s.substring(selEnd));
  196.       data.setLength(oldlen - selLen);
  197.       if (_ATMmode && norm)
  198.         normalize(data);
  199.     }
  200.   }
  201.  
  202.   /* The input is a digit string (possibly with a decimal point) that's presumed
  203.      to be the mantissa of a floating point value whose exponent is the number of digits
  204.      minus the digitsAfterDecimal property.  The incoming decimal point position is
  205.      ignored unless it's found to be correct. It's adjusted to be to the left of
  206.      exactly digitsAfterDecimal digits, with zero fill as required.  Values less
  207.      than unity are given a leading zero.
  208.  
  209.       Examples: (with _digitsAfterDecimal == 2)
  210.  
  211.         Input     Output
  212.          25         0.25
  213.         1.375      13.75
  214.         295.4      29.54
  215.   */
  216.   private void normalize(StringBuffer sigfigs) {
  217.     int decpos = sigfigs.toString().indexOf(_decimalPoint);
  218.     int lod = sigfigs.toString().length() - 1 - _digitsAfterDecimal;  // # digits left of dec. pt.
  219.     if (decpos != lod || decpos < 1) { // if decimal point's absent or in wrong place
  220.       if (decpos != -1) {              // remove existing decimal point
  221.         deleteChar(sigfigs, decpos);
  222.         lod--;              // since first LOD calculation assumed no dec. pt.
  223.       }
  224.       while (lod++ < 0)  // zero fill to right of decimal if needed
  225.         sigfigs.insert(0, '0');
  226.       sigfigs.insert(sigfigs.length() - _digitsAfterDecimal, _decimalPoint);
  227.     }
  228.  
  229.     // Trim down to at most one leadng zero
  230.     while (sigfigs.charAt(0) == '0' && sigfigs.charAt(1) != _decimalPoint)
  231.       deleteChar(sigfigs, 0);
  232.   }
  233.  
  234.   /*  Reformats the parameter to have the right number of digits after the
  235.       decimal point.  The position (or absence) of the decimal point on entry
  236.       determines the exponent.  If the input number of digits to the right
  237.       of the decimal is less than _digitsAfterDecimal, zeros are appended.
  238.       If there are more than _digitsAfterDecimal digits after the decimal,
  239.       rounding is done.
  240.  
  241.       Examples: (with _digitsAfterDecimal == 2)
  242.  
  243.           Input      Output
  244.            25         25.00
  245.           1.375        1.38
  246.           295.4      295.40
  247.          999.995    1000.00
  248.   */
  249.   private void scale(StringBuffer newData) {
  250.     if (newData.length() == 0)
  251.       newData.append('0');
  252.     String data = newData.toString();
  253.     if (_decimalPoint != '.')  // BigDecimal needs "normal" decimal point
  254.       data = data.replace(_decimalPoint, '.');
  255.     data = (new BigDecimal(data)).setScale(_digitsAfterDecimal, BigDecimal.ROUND_HALF_UP).toString();
  256.     if (_decimalPoint != '.')
  257.       data = data.replace('.', _decimalPoint);
  258.     newData.setLength(0);
  259.     newData.append(data);
  260.   }
  261.  
  262.   // Delete the character at the given position from the given StringBuffer
  263.   void deleteChar(StringBuffer data, int pos) {
  264.     int len = data.length();
  265.     if (pos >= 0 && pos < len) {
  266.       String s = data.toString();
  267.       data.insert(0, s.substring(0, pos) + (pos < len -1 ? s.substring(pos + 1) : ""));
  268.       data.setLength(len - 1);
  269.     }
  270.   }
  271.  
  272.   // Variables
  273.   boolean _commas              = true ;  // true to display thousands separator
  274.   boolean _currencySymbolLeads = true ;  // true if currency symbol precedes value
  275.   boolean _ATMmode             = false;  // true for right-to-left data entry
  276.   String  _currencySymbol      = "$"  ;
  277.   char    _decimalPoint        = '.'  ;
  278.   char    _separator           = ','  ;
  279.   int     _digitsAfterDecimal  = 2    ;
  280. }
  281.